using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Controls;
using System.Reflection;
using System.Windows.Data;
using System.Windows.Navigation;
using Ricciolo.PaperBoy.UI.Interop;
using System.Windows.Forms;
using System.Windows;
using System.Runtime.InteropServices;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO;
using System.Diagnostics;
using System.Windows.Interop;
using mshtml;
using Binding=System.Windows.Data.Binding;

namespace Ricciolo.PaperBoy.UI
{
    public class FramePlus : Frame, IOleClientSite
    {
        private static readonly PropertyInfo AxIWebBrowser2Property = Type.GetType("System.Windows.Controls.WebBrowser, PresentationFramework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35").GetProperty("AxIWebBrowser2", BindingFlags.NonPublic | BindingFlags.Instance);
        //private static readonly PropertyInfo ActiveXInstanceProperty = Type.GetType("MS.Internal.Controls.WebBrowser, PresentationFramework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35").GetProperty("ActiveXInstance", BindingFlags.NonPublic | BindingFlags.Instance);

        public static DependencyProperty DocumentTitleProperty = DependencyProperty.Register("DocumentTitle", typeof(String), typeof(FramePlus), new PropertyMetadata("(blank)", new PropertyChangedCallback(OnDocumentTitleChanged)));
        public static RoutedEvent DocumentTitleChangedEvent = EventManager.RegisterRoutedEvent("DocumentTitleChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(FramePlus));

        internal static DependencyPropertyKey NavigationBytesReadPropertyKey = DependencyProperty.RegisterReadOnly("NavigationBytesRead", typeof(Int64), typeof(FramePlus), new FrameworkPropertyMetadata((Int64)0));
        internal static DependencyPropertyKey NavigationMaxBytesPropertyKey = DependencyProperty.RegisterReadOnly("NavigationMaxBytes", typeof(Int64), typeof(FramePlus), new PropertyMetadata((Int64)0));
        internal static DependencyPropertyKey FrameScreenshotPropertyKey = DependencyProperty.RegisterReadOnly("FrameScreenshot", typeof(Brush), typeof(FramePlus), new PropertyMetadata(null));
        internal static DependencyPropertyKey PercentCompletePropertyKey = DependencyProperty.RegisterReadOnly("PercentComplete", typeof(Int32), typeof(FramePlus), new PropertyMetadata(0));

        public static DependencyProperty FrameScreenshotProperty = FrameScreenshotPropertyKey.DependencyProperty;
        public static DependencyProperty NavigationBytesReadProperty = NavigationBytesReadPropertyKey.DependencyProperty;
        public static DependencyProperty NavigationMaxBytesProperty = NavigationMaxBytesPropertyKey.DependencyProperty;
        public static DependencyProperty PercentCompleteProperty = PercentCompletePropertyKey.DependencyProperty;
        public static DependencyProperty StatusTextProperty = DependencyProperty.Register("StatusText", typeof(String), typeof(FramePlus), new PropertyMetadata(null));

        private AxHost.ConnectionPointCookie cookie;
        private WebBrowserEvent webBrowserEvent;

        private static void OnDocumentTitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((FramePlus)d).RaiseEvent(new RoutedEventArgs(DocumentTitleChangedEvent, d));
        }

        protected override void OnContentChanged(object oldContent, object newContent)
        {
            base.OnContentChanged(oldContent, newContent);

            // Reset previous content
            if (this.cookie != null)
            {
                this.cookie.Disconnect();
                this.cookie = null;
                this.ClearValue(DocumentTitleProperty);
            }

            if (!(newContent is HwndHost) && newContent != null)
            {
                Visual page = (Visual)newContent;
                if (page is Page)
                {
                    // Bind page title to DocumentTile
                    Binding binding = new Binding("Title");
                    binding.Source = newContent;
                    binding.Mode = BindingMode.OneWay;
                    BindingOperations.SetBinding(this, DocumentTitleProperty, binding); 
                }
                // Set completed loading
                this.SetValue(PercentCompletePropertyKey, 100);
                // Set brush for screenshot
                this.SetValue(FrameScreenshotPropertyKey, new VisualBrush(page));

                this.AddLogicalChild(page);
            }
            else if (newContent != null)
            {
                // Create new connection
                //object instance = this.ActiveXInstance;
                webBrowserEvent = new WebBrowserEvent(this);
                this.cookie = new AxHost.ConnectionPointCookie(this.WebBrowser, webBrowserEvent, typeof(DWebBrowserEvents2));
            }
        }
       

        public Brush FrameScreenshot
        {
            get { return (Brush)this.GetValue(FrameScreenshotProperty); }
        }

        public Int32 PercentComplete
        {
            get { return (Int32)this.GetValue(PercentCompleteProperty); }
        }

        public Int64 NavigationBytesRead
        {
            get { return (Int64)this.GetValue(NavigationBytesReadProperty); }
        }

        public Int64 NavigationMaxBytes
        {
            get { return (Int64)this.GetValue(NavigationMaxBytesProperty); }
        }

        public String StatusText
        {
            get { return (String)this.GetValue(StatusTextProperty); }
            set {this.SetValue(StatusTextProperty, value);}
        }

        public event RoutedEventHandler DocumentTitleChanged
        {
            add { this.AddHandler(DocumentTitleChangedEvent, value); }
            remove { this.RemoveHandler(DocumentTitleChangedEvent, value); }
        }

        /// <summary>
        /// Get title of current html document
        /// </summary>
        public string DocumentTitle
        {
            get { return (String)this.GetValue(DocumentTitleProperty); }
            internal set { this.SetValue(DocumentTitleProperty, value); }
        }

        internal object ActiveXInstance
        {
            get
            {
                // Ensure activex creation
                object o = this.WebBrowser;
                // Create new connection
                //return ActiveXInstanceProperty.GetValue(this.Content, null);
                return this.Content;
            }
        }

        internal IWebBrowser2 WebBrowser
        {
            get
            {
                if (this.Content == null || !(this.Content is HwndHost))
                    return null;
                return AxIWebBrowser2Property.GetValue(this.Content, null) as IWebBrowser2;
            }
        }

        #region IOleClientSite

        private const int DISPID_AMBIENT_DLCONTROL = -5512;

        [DispId(DISPID_AMBIENT_DLCONTROL)]
        public int setFlags()
        {
            //return 0;
            return (int)(DLCTL.SILENT | DLCTL.FORCEOFFLINE | DLCTL.NO_BEHAVIORS | DLCTL.NO_CLIENTPULL | DLCTL.NO_DLACTIVEXCTLS | DLCTL.NO_FRAMEDOWNLOAD | DLCTL.NO_JAVA | DLCTL.NO_RUNACTIVEXCTLS | DLCTL.NO_SCRIPTS);
        }

        int IOleClientSite.SaveObject()
        {
            return 0;
        }

        int IOleClientSite.GetMoniker(int dwAssign, int dwWhichMoniker, out object moniker)
        {
            moniker = null;
            return 0;
        }

        public const int E_NoInterface = unchecked((int)0x80040215);

        int IOleClientSite.GetContainer(out IOleContainer container)
        {
            container = null;
            return E_NoInterface;
        }

        int IOleClientSite.ShowObject()
        {
            return 0;
        }

        int IOleClientSite.OnShowWindow(int fShow)
        {
            return 0;
        }

        int IOleClientSite.RequestNewObjectLayout()
        {
            return 0;
        }

        #endregion

    }

    internal class WebBrowserEvent : DWebBrowserEvents2
    {

        private FramePlus frame;

        public WebBrowserEvent(FramePlus frame)
        {
            this.frame = frame;
        }

        #region DWebBrowserEvents2 Members

        public void StatusTextChange(string text)
        {
            this.frame.SetValue(FramePlus.StatusTextProperty, text);
        }

        private int lastPercent;
        private bool progressComplete = false;

        public void ProgressChange(int progress, int progressMax)
        {
            // This method is called also for frames and others resources
            if (progressComplete) return;

            this.frame.SetValue(FramePlus.NavigationBytesReadPropertyKey, (Int64)progress);
            this.frame.SetValue(FramePlus.NavigationMaxBytesPropertyKey, (Int64)progressMax);

            if (progressMax > 0)
            {
                int percent = Math.Min((progress * 100 / progressMax), 100);
                // Skip previous percent
                //if (percent < lastPercent) return;

                this.frame.SetValue(FramePlus.PercentCompletePropertyKey, percent);

                // Max tens screenshot
                if (percent > lastPercent + 10)
                {
                    TakeScreenshot();
                }
                lastPercent = percent;

                // Mark as complete
                progressComplete = (percent == 100);
            }
        }

        private void TakeScreenshot()
        {
            ImageSource source = GetBitmap();
            if (source != null)
                this.frame.SetValue(FramePlus.FrameScreenshotPropertyKey, new ImageBrush(source));
        }

        private BitmapSource GetBitmap()
        {
            HwndHost host = this.frame.Content as HwndHost;
            if (host == null) return null;

            HandleRef hwnd = new HandleRef(host, host.Handle);
            if ((hwnd.Handle != IntPtr.Zero) && UnsafeNativeMethods.IsWindow(hwnd))
            {
                // Get window rect
                NativeMethods.RECT rect = new NativeMethods.RECT();
                SafeNativeMethods.GetWindowRect(hwnd, out rect);

                // Compute width and height
                int width = rect.right - rect.left;
                int height = rect.bottom - rect.top;

                // Get webBrowser DC
                HandleRef hostDC = new HandleRef(host, UnsafeNativeMethods.GetDC(hwnd));
                if (hostDC.Handle == IntPtr.Zero)
                    return null;

                HandleRef bmpDC = new HandleRef(host, IntPtr.Zero);
                HandleRef bmp = new HandleRef(host, IntPtr.Zero);
                try
                {
                    // Create compatible DC
                    bmpDC = new HandleRef(host, UnsafeNativeMethods.CriticalCreateCompatibleDC(hostDC));
                    if (bmpDC.Handle == IntPtr.Zero)
                        return null;

                    // Create compatible bitmap
                    bmp = new HandleRef(host, UnsafeNativeMethods.CriticalCreateCompatibleBitmap(hostDC, width, height));
                    if (bmp.Handle == IntPtr.Zero)
                        return null;

                    // Associate DC to bitmap
                    IntPtr ptr1 = UnsafeNativeMethods.CriticalSelectObject(bmpDC, bmp.Handle);
                    try
                    {
                        // Clear DC
                        NativeMethods.RECT rect2 = new NativeMethods.RECT(0, 0, width, height);
                        IntPtr ptr2 = UnsafeNativeMethods.CriticalGetStockObject(0);
                        UnsafeNativeMethods.CriticalFillRect(bmpDC.Handle, ref rect2, ptr2);

                        // Get the document
                        IHTMLDocument2 document = (IHTMLDocument2)this.frame.WebBrowser.Document; ;
                        if (document == null) return null;

                        // Get body
                        Ricciolo.PaperBoy.UI.Interop.IHTMLElementRender render = document.body as Ricciolo.PaperBoy.UI.Interop.IHTMLElementRender;
                        if (render == null) return null;

                        // Draw to DC
                        render.DrawToDC(bmpDC.Handle);

                        // Create Bitmap source
                        BitmapSource bmpSource = (BitmapSource)Type.GetType("System.Windows.Interop.Imaging, PresentationCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35").GetMethod("CriticalCreateBitmapSourceFromHBitmap", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, new object[] { bmp.Handle, IntPtr.Zero, Int32Rect.Empty, null, 2 });
                        return bmpSource;
                    }
                    finally
                    {
                        UnsafeNativeMethods.CriticalSelectObject(bmpDC, ptr1);
                    }
                }
                finally
                {
                    UnsafeNativeMethods.ReleaseDC(hwnd, hostDC);
                    hostDC = new HandleRef(null, IntPtr.Zero);
                    if (bmp.Handle != IntPtr.Zero)
                    {
                        UnsafeNativeMethods.DeleteObject(bmp);
                        bmp = new HandleRef(host, IntPtr.Zero);
                    }
                    if (bmpDC.Handle != IntPtr.Zero)
                    {
                        UnsafeNativeMethods.CriticalDeleteDC(bmpDC);
                        bmpDC = new HandleRef(host, IntPtr.Zero);
                    }
                }
            }

            return null;
        }

        public void CommandStateChange(long command, bool enable)
        {
        }

        public void DownloadBegin()
        {
        }

        public void DownloadComplete()
        {
        }

        public void TitleChange(string text)
        {
            this.frame.SetValue(FramePlus.DocumentTitleProperty, text);
        }

        public void PropertyChange(string szProperty)
        {
        }

        public void BeforeNavigate2(object pDisp, ref object URL, ref object flags, ref object targetFrameName, ref object postData, ref object headers, ref bool cancel)
        {
            // Only for the main document
            if (((IWebBrowser2)pDisp) == frame.WebBrowser)
                Reset();
        }

        internal void Reset()
        {
            Debug.WriteLine("Reset");
            lastPercent = 0;
            progressComplete = false;
            this.frame.SetValue(FramePlus.FrameScreenshotPropertyKey, null);
            this.frame.SetValue(FramePlus.PercentCompletePropertyKey, 0);
            this.frame.SetValue(FramePlus.NavigationBytesReadPropertyKey, (Int64)0);
            this.frame.SetValue(FramePlus.NavigationMaxBytesPropertyKey, (Int64)0);
        }

        public void NewWindow2(ref object pDisp, ref bool cancel)
        {
        }

        public void NavigateComplete2(object pDisp, ref object URL)
        {
            TakeScreenshot();
        }

        public void DocumentComplete(object pDisp, ref object URL)
        {
            TakeScreenshot();
        }

        public void OnQuit()
        {
        }

        public void OnVisible(bool visible)
        {
        }

        public void OnToolBar(bool toolBar)
        {
        }

        public void OnMenuBar(bool menuBar)
        {
        }

        public void OnStatusBar(bool statusBar)
        {
        }

        public void OnFullScreen(bool fullScreen)
        {
        }

        public void OnTheaterMode(bool theaterMode)
        {
        }

        public void WindowSetResizable(bool resizable)
        {
        }

        public void WindowSetLeft(int left)
        {
        }

        public void WindowSetTop(int top)
        {
        }

        public void WindowSetWidth(int width)
        {
        }

        public void WindowSetHeight(int height)
        {
        }

        public void WindowClosing(bool isChildWindow, ref bool cancel)
        {
        }

        public void ClientToHostWindow(ref long cx, ref long cy)
        {
        }

        public void SetSecureLockIcon(int secureLockIcon)
        {

        }

        public void FileDownload(ref bool cancel)
        {
        }

        public void NavigateError(object pDisp, ref object URL, ref object frame, ref object statusCode, ref bool cancel)
        {
            cancel = true;
        }

        public void PrintTemplateInstantiation(object pDisp)
        {
        }

        public void PrintTemplateTeardown(object pDisp)
        {
        }

        public void UpdatePageStatus(object pDisp, ref object nPage, ref object fDone)
        {
        }

        public void PrivacyImpactedStateChange(bool bImpacted)
        {
        }

        #endregion
    }
}
